﻿namespace CacheBehaviorExtension
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.ServiceModel.Dispatcher;
    using System.Text;
    using System.Xml.Serialization;
    using CacheProvider;
    using CacheProviderEntities;

    public class CachingOperationInvoker : IOperationInvoker
    {
        private IOperationInvoker _originalOperationInvoker;
        private TimeSpan _timeout;
        private string _key;
        private string _regionName;
        private List<string> _cacheTags;
        private DependencyInfo _dependencyInfo;


        public CachingOperationInvoker(IOperationInvoker originalOperationInvoker, string key, string regionName, TimeSpan timeout, List<string> cacheTags, DependencyInfo dependencyInfo)
        {
            this._originalOperationInvoker = originalOperationInvoker;
            this._key = key;
            this._regionName = regionName;
            this._cacheTags = cacheTags;
            this._dependencyInfo = dependencyInfo;
            this._timeout = timeout;
        }


        #region IOperationInvoker Members

        public object[] AllocateInputs()
        {
            return this._originalOperationInvoker.AllocateInputs();
        }

        public object Invoke(object instance, object[] inputs, out object[] outputs)
        {
            string key = string.IsNullOrEmpty(this._key) ? this.GenerateKey(inputs) : _key;

            object valueFromCache;

            if (CacheBroker.IsRegionSupported && !string.IsNullOrEmpty(this._regionName))
            {
                valueFromCache = CacheBroker.Get(key, this._regionName);
            }
            else
            {
                valueFromCache = CacheBroker.Get(key);
            }

            if (valueFromCache != null)
            {
                outputs = new object[0];
                //Uncomment the following statements to produce Sliding Expiration behavior 
                //if (this._dependencyInfo != null)
                //    CacheBroker.Put(this._key, valueFromCache, this._regionName, this._dependencyInfo, this._timeout);
                //else
                //    CacheBroker.Put(this._key, valueFromCache, this._timeout, this._regionName);

                return valueFromCache;
            }

            object value = this._originalOperationInvoker.Invoke(instance, inputs, out outputs);

            if (!string.IsNullOrEmpty(this._regionName) && CacheBroker.IsRegionSupported)
            {
                if (this._dependencyInfo != null)
                    CacheBroker.Put(key, value, this._regionName, this._dependencyInfo, this._timeout);
                else
                    CacheBroker.Put(key, valueFromCache, this._timeout, this._regionName);
            }
            else
            {
                if (this._dependencyInfo != null)
                    CacheBroker.Put(key, value, this._dependencyInfo, this._timeout);
                else
                    CacheBroker.Put(key, value, this._dependencyInfo, this._timeout);
            }

            return value;
        }

        private string GenerateKey(object[] inputs)
        {
            StringBuilder stringBuilder = new StringBuilder();
            foreach (object input in inputs)
            {
                var xmlSerializer = new XmlSerializer(input.GetType());
                using (var memStream = new MemoryStream())
                {
                    xmlSerializer.Serialize(memStream, input);
                    string inputAsString = Encoding.Default.GetString(memStream.GetBuffer());
                    stringBuilder.Append(inputAsString);
                }
            }
            return stringBuilder.ToString();
        }

        public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
        {
            return this._originalOperationInvoker.InvokeBegin(instance, inputs, callback, state);
        }

        public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
        {
            return this._originalOperationInvoker.InvokeEnd(instance, out outputs, result);
        }

        public bool IsSynchronous
        {
            get
            {
                return this._originalOperationInvoker.IsSynchronous;
            }
        }

        #endregion
    }
}
